package demo.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO模型
 */
public class NIOServer {
    public static void main(String[] args) {
        try {
            // 1. 创建一个selector选择器
            Selector selector = Selector.open();
            // 2. 创建一个socket通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 为什么要明确配置非阻塞模式呢？这是因为阻塞模式下，注册模式是不允许的，会抛出IllegalBlockingModeException异常；
            serverSocketChannel.configureBlocking(false); // 设置为非阻塞的方式
            serverSocketChannel.socket().bind(new InetSocketAddress(8900));
            // 3. 把channel注册到selector选择器上
            // 通过指定的SelectionKey.OP_ACCEPT,告诉调度员，它关注的是新的连接请求；
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            // 4. select 轮询等待连接或者发送数据
            while (true) {
                System.out.println("等待客户端链接...");
                // 如果下面channel接收完数据不关闭通道的话，数据通道在这里是一直存在的。
                // 也就是说这里不会阻塞，因为selector能取到活动的通道
                selector.select(); // 线程阻塞，等待客户端连接，或者数据接收，
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove(); // key用完就删除
                    // 5. 处理请求
                    handle(serverSocketChannel, key);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void handle(ServerSocketChannel serverChannel, SelectionKey key) throws IOException{
        // 有新连接进来就创建一个接收数据的通道并注册到selector上
        if(key.isAcceptable()) {
            // 通过ServerSocketChannel.accept()方法接受新的连接，这将创建一个新的SocketChannel实例。
            SocketChannel socketChannel = serverChannel.accept();
            socketChannel.configureBlocking(false);
            // 这个新的通道用来接收数据，每有一个新的连接进来，就创建一个通道用来接收数据
            socketChannel.register(key.selector(),SelectionKey.OP_READ);
            System.out.println("新的客户端连接：" + socketChannel.hashCode());
        }
        // 有数据从通道进来就从通道接收数据，接受完关闭通道
        else if (key.isReadable()) {
            // 这里取到的SocketChannel就是上面通过ServerSocketChannel创建的SocketChannel
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(buffer);
            if(len!=-1) {
                System.out.println("接收到【" + socketChannel.hashCode() + "】数据:"+new String(buffer.array(),0,len));
            }
            buffer.clear(); // 清空buffer，等待下一次读取
            // 给客户端发送信息
            String returnMsg = "hello!";
            System.out.println("给【" + socketChannel.hashCode() + "】返回数据：" + returnMsg);
            ByteBuffer wrap = ByteBuffer.wrap(returnMsg.getBytes());
            socketChannel.write(wrap);
            // 表示你希望当通道有数据可读或准备好接受写入时，选择器能够选择这个通道，以便你可以进行相应的读写操作。
            key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
            // 接受完消息就关闭通道
            socketChannel.close();
        }
    }
}
